package cn.common.config;

/**
 * Copyright
 *
 * @Author WuHuang
 * @Date 2022/12/22
 */

import java.net.InetAddress;
import java.util.Random;

/**
 * @Classname IdCreater
 * @Description id生成器
 * @Date 2020-01-17 17:39
 * @Created by jacksondeng
 */
public class IdCreater {
    /**
     * 最后生成的事假戳
     */
    private static long lastTimestamp = -1L;
    /**
     * 递增序列数
     */
    private long sequence = 0L;
    /**
     * 业务id
     */
    private final long businessId;
    /**
     * 库表位
     */
    private final long datacenterId;
    /**
     * 预留位
     */
    private final long reserved;

    public IdCreater(long businessId) {
        if (businessId <= 15L && businessId >= 0L) {
            this.businessId = businessId;
            this.datacenterId = 0L;
            this.reserved = 0L;
        } else {
            throw new IllegalArgumentException("worker Id can't be greater than 31 or less than 0");
        }
    }



    public long nextId() {
//        return this.nextId(false,  this.businessId,0L,0L);
        return this.nextId(new Random().nextInt(255));
    }

    /**
     * 只传分库分表标识,用IP尾号隔离
     * @param datacenterId
     * @return
     */
    public long nextId(long datacenterId) {
        if (datacenterId < 0 || datacenterId > 255) {
            throw new IllegalArgumentException("datacenter Id can't be greater than 255 or less than 0");
        }
        return this.nextId(true,datacenterId);
    }

    /**
     *
     * @param businessId
     * @see com.tencent.sr.rmall.springbootstarter.enums.EBusinessTypeEnum
     * @param datacenterId 分库分表字段值的后8位，注意这个8位是二进制位
     * @return
     */
    public long nextId(long businessId, long datacenterId) {
        if (businessId < 0 || businessId > 15) {
            throw new IllegalArgumentException("businessId Id can't be greater than 15 or less than 0");
        }

        if (datacenterId < 0 || datacenterId > 255) {
            throw new IllegalArgumentException("datacenter Id can't be greater than 255 or less than 0");
        }

        return this.nextId(true, businessId, datacenterId, 0L);
    }

    public long nextId(long businessId, long datacenterId, long reserved) {
        if (businessId < 0 || businessId > 15) {
            throw new IllegalArgumentException("businessId Id can't be greater than 15 or less than 0");
        }

        if (datacenterId < 0 || datacenterId > 255) {
            throw new IllegalArgumentException("datacenter Id can't be greater than 255 or less than 0");
        }

        if (reserved < 0 || reserved > 15) {
            throw new IllegalArgumentException("reserved Id can't be greater than 15 or less than 0");
        }
        return this.nextId(true, businessId, datacenterId, reserved);
    }

    private synchronized long nextId(boolean isPadding, long businessId, long datacenterId, long reserved) {
        long timestamp = this.timeGen();
        long paddingnum = this.datacenterId;
        long paddBusinessId = this.businessId;
        long paddReserved = this.reserved;
        if (isPadding) {
            paddingnum = datacenterId;
            paddBusinessId = businessId;
            paddReserved = reserved;
        }

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        } else {
            this.sequence = this.sequence + 1L & 255L;
            if (lastTimestamp == timestamp && this.sequence == 0L) {
                timestamp = this.tilNextMillis(lastTimestamp);
            }

            lastTimestamp = timestamp;
            long nextId = 0;
            if (isPadding) {
                nextId = timestamp - 1579249489468L << 24 | paddReserved << 20 | paddBusinessId << 16 | this.sequence << 8 | paddingnum;
            } else {
                nextId = timestamp - 1579249489468L << 24 | paddReserved << 20 | paddBusinessId << 16 | paddingnum << 8 | this.sequence;
            }
            return nextId;
        }
    }

    /**
     * 此方法通过IP尾号隔离
     * @param isPadding
     * @param datacenterId
     * @return
     */
    private synchronized long nextId(boolean isPadding,long datacenterId) {
        long timestamp = this.timeGen();
        long paddingnum = this.datacenterId;
        long paddBusinessId = this.businessId;
        if (isPadding) {
            paddingnum = datacenterId;
            paddBusinessId = getIpTail();
        }
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        } else {
            this.sequence = this.sequence + 1L & 255L;
            if (lastTimestamp == timestamp && this.sequence == 0L) {
                timestamp = this.tilNextMillis(lastTimestamp);
            }
            lastTimestamp = timestamp;
            long nextId = 0;
            if (isPadding) {
                nextId = timestamp - 1579249489468L << 24 | paddBusinessId << 16 | this.sequence << 8 | paddingnum;
            } else {
                nextId = timestamp - 1579249489468L << 24 | paddBusinessId << 16 | paddingnum << 8 | this.sequence;
            }
            return nextId;
        }
    }

    private static int  getIpTail() {
        int ipTail = 0;
        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            int length = ip.length();
            int startIndex = ip.lastIndexOf(".");
            ipTail = Integer.valueOf(ip.substring(startIndex + 1, length));
            return ipTail;
        } catch (Exception var1) {
            var1.printStackTrace();
            ipTail = new Random().nextInt(255);
        }
        return ipTail;
    }



    private long tilNextMillis(long lastTimestamp) {
        long timestamp;
        for (timestamp = this.timeGen(); timestamp <= lastTimestamp; timestamp = this.timeGen()) {
        }

        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}

